package Math::Calculus::FalsePosition;

use strict;
use warnings;

use Moose;
use Math::Calculus::Expression;

our $VERSION = '0.01';

#TODO reclamar se a exp tiver mais de uma var
has expression => ( isa => 'Str', is => 'rw', required => 1 );

#TODO reclamar se a variável não corresponder à da exp
has variable => ( isa => 'Str', is => 'rw', required => 1 );
has start    => ( isa => 'Num', is => 'rw', required => 1 );
has end      => ( isa => 'Num', is => 'rw', required => 1 );

#TODO receber dois valores de precisão: i. para f(x); ii. para o intervalo
has precision => ( isa => 'Num', is => 'rw', required => 1 );

#atualmente apenas i está implementado

has partial_results => ( is => 'rw', default => sub { [] } );

sub BUILD {
    my $self = shift;
    $self->validate;
}

sub validate {
    my $self = shift;
    my $exp  = Math::Calculus::Expression->new;
    $exp->addVariable( my $var = $self->variable ) or die $exp->getError;
    $exp->setExpression( $self->expression );
    my ( $start, $end, $e ) = ( $self->start, $self->end, $self->precision );
    die \"Invalid interval: [$start, $end]"
        if $start >= $end
        || $exp->evaluate( $var => $start ) * $exp->evaluate( $var => $end )
        > 0;
    return 1;
}

sub run {    #TODO implementar criterio de parada para precisão do intervalo
    my $self = shift;
    my $exp  = Math::Calculus::Expression->new;
    $exp->addVariable( $self->variable )     or die $exp->getError;
    $exp->setExpression( $self->expression ) or die $exp->getError;
    my ( $a, $b, $e ) = ( $self->start, $self->end, 10**-$self->precision );
    my ( $interv, $m ) = ( $b - $a, $exp->evaluate( x => $a ) );
    my ( $fa, $fb )
        = ( $exp->evaluate( x => $a ), $exp->evaluate( x => $b ) );
    push @{ $self->partial_results }, { x => $a, y => $fa };
    push @{ $self->partial_results }, { x => $b, y => $fb };
    my $x = ( $a * $fb - $b * $fa ) / ( $fb - $fa );
    my $fx = $exp->evaluate( x => $x );
    push @{ $self->partial_results }, { x => $x, y => $fx };

    while ( abs $fx > $e ) {
        if ( $m * $fx > 0 ) {
            $a = $x;
            $fa = $exp->evaluate( x => $a );
        }
        else {
            $b = $x;
            $fb = $exp->evaluate( x => $b );
        }
        $interv = $b - $a;
        $x      = ( $a * $fb - $b * $fa ) / ( $fb - $fa );
        $fx     = $exp->evaluate( x => $x );
        push @{ $self->partial_results }, { x => $x, y => $fx };
    }
    return $x;
}

1;
__END__


=head1 NAME

Math::Calculus::FalsePosition - False Position Method

=head1 SYNOPSIS

  use Math::Calculus::FalsePosition;

  my ($exp, $var) = ('x^2 + x - 6', 'x');
  my ($start, $end, $e) = (1, 3, 14);

  my $method = Math::Calculus::FalsePosition->new(
     expression => $exp,
     variable   => $var,
     start      => $start,
     end        => $end,
     precision  => $e
  );

  my $result = $method->run;
  print $result; #prints 

=head1 DESCRIPTION

Perl extension for numerical finding the roots of equations using false position method.

=head2 EXPORT

None by default.

=head1 SEE ALSO

Math::Calculus::Expression, Math::Calculus::NewtonRaphson, Math::Calculus::Bisection

=head1 AUTHORS

Wallace Reis E<lt>wreis@cpan.orgE<gt>, Eden Cardim E<lt>edenc@cpan.orgE<gt>

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2007 by Eden Cardim & Wallace Reis

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.8.8 or,
at your option, any later version of Perl 5 you may have available.

=cut
